//#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <linux/fs.h>
#include <sys/ioctl.h>

int open_disk(char *dev)
{
	int fd;
	fd = open(dev, O_RDWR|O_EXCL|O_SYNC|O_DIRECT|O_LARGEFILE, 0666); /*O_DIRECT | O_LARGEFILE*/
	if (fd < 0)
	{
		printf("open failed, %s\n", strerror(errno));
		return -1;
	}
	return fd;
}

void release_disk(int fd)
{
	close(fd);
}

off64_t get_disk_size(int fd)
{
	off64_t length=0;
	//get disk size for test
	if (ioctl(fd, BLKGETSIZE64, &length) < 0)
	{
		printf("get size failed, %s\n", strerror(errno));
		return -1;
	}
	//printf("disk size %llx\n", length); //? TODO
	return length;
}

int write_disk(int fd, off64_t offset, unsigned char *buf, int len)
{
	int ret=-1;
	//seek
	if (lseek64(fd, offset, SEEK_SET) < 0)
	{
		printf("lseek64 failed at %llx, %s\n", offset, strerror(errno));
		return -1;
	}

	//write data
	if ((ret = write(fd, buf, len)) < 0)
	{
		printf("write failed at %llx, %s\n", offset, strerror(errno));
		return -1;
	}	

	return ret;
}

int read_disk(int fd, off64_t offset, unsigned char *buf, int len)
{
	int ret=-1;
	//seek
	if (lseek64(fd, offset, SEEK_SET) < 0)
	{
		printf("lseek64 failed at %llx, %s\n", offset, strerror(errno));
		return -1;
	}

	//read data
	if ((ret = read(fd, buf, len)) < 0)
	{
		printf("read failed at %llx, %s\n", offset, strerror(errno));
		return -1;
	}	

	return ret;
}

int erase_disk(int fd, off64_t offset, off64_t len)
{
	int ret = -1;
	off64_t range[2];
	
	range[0] = offset;
	range[1] = len;
	//erase
	if ((ret = ioctl(fd, BLKDISCARD, range)) < 0)
	{
		printf("erase failed, %s\n", strerror(errno));
		return -1;
	}		
	return ret;
}

#define CHKSZ 14 	//(3G)
#define RANGE 0x100000LL //(128M)
	
int main(int argc, char** argv)
{
	unsigned char *buf, *buf_ori;
	//char buf_tmp[512];
	unsigned int offset, i, j;
	int fd;
	off64_t total_block,addr;
	//off64_t mmc_offset=0x40000000LL; //1g

	off64_t erase_off,erase_len;
	
	#define BLK_1G (2048*1024LL)
	#define BLK_128M (2048*64LL) 
	#define SEC_SIZE 512LL
	
	printf("mmc_test V1.0 build@%s_%s\n", __TIME__, __DATE__);
		
	buf_ori = malloc(8*1024);
	offset = 512 - ((unsigned long)buf_ori & 0x1ff);
	buf = buf_ori + offset;

	//1.open disk
	fd = open_disk("/dev/mmcblk0");
	if(fd < 0) {
		printf("no tf card dev!\n");
		return -1; 
	}

	//2.get disk total block
	total_block = get_disk_size(fd) / SEC_SIZE;
	printf("total_block : %llx\n", total_block);
	printf("check tf card begin\n");
	
	//3. write 1 block at the end of each 1GB
	//printf("write 1 block at the end of each 1GB\n"); 
	for(i=1,addr=0; addr<total_block; i++)
	{
		addr = i*BLK_1G < total_block ? i*BLK_1G : total_block;
		memset(buf, i+i*0x10, SEC_SIZE);

		printf("-");
		fflush(stdout);
	//	printf("write_disk\n");
		if(write_disk(fd, (addr-1)*SEC_SIZE, buf, SEC_SIZE) < 0) {
	//		printf("write_disk error!\n");
			goto err;
		}
	}	
	printf("\n");
	#if 0
	//4.read 1 block at the end of each 1GB
	printf("read 1 block at the end of each 1GB\n");
	for(i=1,addr=0; addr<total_block; i++)
	{
		memset(buf_tmp, i+i*0x10, SEC_SIZE);
		addr = i*BLK_1G < total_block ? i*BLK_1G : total_block;
		if (read_disk(fd, (addr-1)*SEC_SIZE, buf, SEC_SIZE) < 0){
			printf("read_disk error!\n");			
			goto err;
		}

		printf("%02d\t", i);
		for (j=0; j<8; j++)
		{
			printf("%02x ",buf[j]);
				if((j+1)%8==0)
					printf("\n");		
		}
		printf("\n");
	}
	#endif

	//5. erase by 1GB
	//printf("erase by 1GB\n");
	for(i=1,addr=0; addr<total_block; i++)
	{
	//	printf("erase %dGB size \n", i);
		addr = i*BLK_1G < total_block ? i*BLK_1G : total_block;
		
		erase_off = (i-1)*BLK_1G*SEC_SIZE;
		erase_len = addr*SEC_SIZE - erase_off;

	//	printf("addr : %lld\n",addr);
	//	printf("erase_off : %lld\n",erase_off);
	//	printf("erase_len : %lld\n",erase_len);
		printf(".");
		fflush(stdout);
		if (erase_disk(fd, erase_off, erase_len) < 0){
	//		printf("erase_disk error!\n");	
			goto err;
		}
	}

	//6.verify by 1GB
	for(i=1,addr=0; addr<total_block; i++)
	{
		addr = i*BLK_1G < total_block ? i*BLK_1G : total_block;
		if (read_disk(fd, (addr-1)*SEC_SIZE, buf, SEC_SIZE) < 0){
			printf("read_disk error!\n");			
			goto err;
		}
		
	//	printf("%02d\t", i);
		for (j=0; j<8; j++)
		{
	//		printf("%02x ",buf[j]);
			if (buf[j] != 0xff && buf[j] != 0x00)
				goto err;
		}
	//	printf("\n");
	}	

	free(buf_ori);
	close(fd);
	printf("\n");	
	printf("##########################################\n");
	printf("############### GOOD CARD ################\n");
	printf("##########################################\n");	
	return 0;
	
err:
	close(fd);
	free(buf_ori);
	printf("\n");	
	printf("##########################################\n");
	printf("############## ERROR CARD ################\n");
	printf("##########################################\n");
	return -1;
	
}

